
#include "Grass.hpp"
#include <glm/gtc/random.hpp>
#include "Image.hpp" //


Grass::Grass()
{
}

Grass::~Grass()
{
}

bool Grass::init(const aer::Heightmap &heightmap)
{
  initShader();
  initTexture();
  initGeometry(heightmap);
  
  return true;//
}

void Grass::initShader()
{
  m_program.generate();
  m_program.addShader( aer::Program::VERTEX_SHADER,   "Grass.Vertex");
  m_program.addShader( aer::Program::GEOMETRY_SHADER, "Grass.Geometry");
  m_program.addShader( aer::Program::FRAGMENT_SHADER, "Grass.Fragment");
  m_program.link();
}

void Grass::initTexture()
{
  Image_t img;
  
  img.load( "../IslandGL4/data/grass.tga" );
  
  m_texture.generate();
  m_texture.bind();
    m_texture.setMipmapFilter( aer::Texture::LINEAR, aer::Texture::LINEAR);
    m_texture.setWraps( aer::Texture::CLAMP_TO_EDGE, aer::Texture::CLAMP_TO_EDGE);
    
    //aer::Texture::Image2D( img.internalFormat, img.width, img.height, img.format, img.type, img.data);
    m_texture.setImage( aer::Texture::TEXTURE_2D, 
                        img.internalFormat, img.width, img.height, img.format, img.type, img.data);
    glGenerateMipmap( GL_TEXTURE_2D ); //
  m_texture.unbind();
}

void Grass::initGeometry(const aer::Heightmap &heightmap)
{
  const aer::Heightmap::HMVertex *hmvertices = heightmap.getVertices();
  
  
  // count the number vertices in the 'grass level' range
  m_grassCount = 0u;  
  for (int y=0, index=0; y<heightmap.getHeight(); ++y)
  {
    for (int x=0; x<heightmap.getWidth(); ++x, ++index)
    {
      if ((hmvertices[index].position.y > kLowerThreshold) &&
          (hmvertices[index].position.y < kUpperThreshold))   // XXX
      {
        ++m_grassCount;
      }
    }
  }  
  
  
  AER_CHECK( m_grassCount > 0 );
  
  m_vao.generate();
  m_vao.bind();
  
  m_vbo.generate();
  m_vbo.bind( aer::DeviceBuffer::ARRAY );
  
  
  // Position
  aer::U32 stride = 8*sizeof(float);
  aer::U32 bufferSize = stride * m_grassCount;
  m_vbo.resize( bufferSize, aer::DeviceBuffer::STATIC_DRAW);
    
  float *pVBO = (float*)glMapBuffer( GL_ARRAY_BUFFER, GL_WRITE_ONLY);
  
  int gIdx = 0;
  for (int y=0, index=0; y<heightmap.getHeight(); ++y)
  {
    for (int x=0; x<heightmap.getWidth(); ++x, ++index)
    {
      aer::Vector3 vertex = hmvertices[index].position;
      
      if ((vertex.y <= kLowerThreshold) || 
          (vertex.y >= kUpperThreshold)) { //
        continue;
      }
      if (gIdx >= m_grassCount) break;
      
      aer::Vector4 random = glm::linearRand( aer::Vector4(-0.5f), aer::Vector4(0.5f));
      
      // Position
      const float radius = 12.0f;//
      vertex.x += random.x * radius;
      vertex.z += random.z * radius;
      vertex.y = heightmap.getLocationHeight( aer::Vector2( vertex.x, vertex.z) );
                 
      // height
      const float grassMinHeight = 1.0f;
      const float grassHeightScale = 3.5f;
      float grassHeight = grassMinHeight + (0.5f+random.y) * grassHeightScale;
      
      // jitter
      float jitter = glm::clamp( 0.5f+random.w, 0.25f, 1.0f);
      
      // color
      float dH = (vertex.y-kLowerThreshold)/(kUpperThreshold-kLowerThreshold);//
      dH = glm::abs(dH); //
      
      const aer::Vector3 bottomColor( 1.0f, 0.7f, 0.8f);
      const aer::Vector3 topColor( 0.8f, 1.5f, 0.75f);
      
      aer::Vector3 color = glm::mix( bottomColor, topColor, dH);
      color *= aer::Vector3( 0.5f+random.x, 1.0f, 0.5f+random.z);
      
      int id = 8*gIdx;
      pVBO[id+0] = vertex.x;
      pVBO[id+1] = vertex.y;
      pVBO[id+2] = vertex.z;
      pVBO[id+3] = grassHeight;
      pVBO[id+4] = jitter;
      pVBO[id+5] = color.r;
      pVBO[id+6] = color.g;
      pVBO[id+7] = color.b;      
      
      ++gIdx;      
    }
  }
  
  glUnmapBuffer( GL_ARRAY_BUFFER );
  
  
  glEnableVertexAttribArray( 0 );
  glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, stride, 0);
  
  glEnableVertexAttribArray( 1 );
  glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, stride, (void*)(3*sizeof(float)));
  
  glEnableVertexAttribArray( 2 );
  glVertexAttribPointer( 2, 3, GL_FLOAT, GL_FALSE, stride, (void*)(5*sizeof(float)));
  
  
  m_vao.unbind();
}

void Grass::render(const aer::Camera &camera)
{  
  //glEnable( GL_MULTISAMPLE );
  //glEnable( GL_SAMPLE_ALPHA_TO_COVERAGE );
  
  m_texture.bind();
  
  m_program.bind();
  {
    static unsigned time=0u;
    m_program.setUniform( "uViewProjMatrix", camera.getViewProjectionMatrix());
    m_program.setUniform( "uEyePosition", camera.getPosition());
    m_program.setUniform( "uElapsedTime", ++time/100.f);    
    
    m_vao.bind();
      glDrawArrays( GL_POINTS, 0, m_grassCount );//
    m_vao.unbind();
  }
  m_program.unbind();
  
  m_texture.unbind();
  
  //glDisable( GL_MULTISAMPLE );
  //glDisable( GL_SAMPLE_ALPHA_TO_COVERAGE );
}
